﻿/* GraphicsExtension - [Extended Graphics]
 * Author name:           Arun Reginald Zaheeruddin
 * Current version:       1.0.0.4 (12b)
 * Release documentation: http://www.codeproject.com
 * License information:   Microsoft Public License (Ms-PL) [http://www.opensource.org/licenses/ms-pl.html]
 * 
 * Enhancements and history
 * ------------------------
 * 1.0.0.1 (20 Jul 2009): Initial release with modified code from previous CodeProject article.
 * 1.0.0.2 (25 Jul 2009): Added functionality that allows selected corners on a rectangle to be rounded.
 *                        Modified code to adapt to an anti-aliased output while drawing and filling rounded rectangles.
 * 1.0.0.3 (26 Jul 2009): Added DrawRoundedRectangle and FillRoundedRectangle methods that take a Rectangle and RectangleF object.
 * 1.0.0.4 (27 Jul 2009): Added font metrics and measuring utility that measures a font's height, leading, ascent, etc.
 * 
 * Issues addressed
 * ----------------
 * 1. Rounded rectangles - rounding edges of a rectangle.
 * 2. Font Metrics - Measuring a font's height, leading, ascent, etc.
 */

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace CloudShot.Utils.Drawings
{
  public static class RoundedRectangleDrawingExtensions
  {
    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, float x, float y, float width, float height, float radius,
                                            RectangleEdgeFilter filter)
    {
      RectangleF rectangle = new RectangleF(x, y, width, height);
      GraphicsPath path = graphics.GenerateRoundedRectangle(rectangle, radius, filter);
      SmoothingMode old = graphics.SmoothingMode;
      graphics.SmoothingMode = SmoothingMode.HighQuality;
      graphics.DrawPath(pen, path);
      graphics.SmoothingMode = old;
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, float x, float y, float width, float height, float radius)
    {
      graphics.DrawRoundedRectangle(pen, x, y, width, height, radius, RectangleEdgeFilter.All);
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, int x, int y, int width, int height, int radius)
    {
      graphics.DrawRoundedRectangle(pen, Convert.ToSingle(x), Convert.ToSingle(y), Convert.ToSingle(width), Convert.ToSingle(height), Convert.ToSingle(radius));
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, Rectangle rectangle, int radius, RectangleEdgeFilter filter)
    {
      graphics.DrawRoundedRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, filter);
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, Rectangle rectangle, int radius)
    {
      graphics.DrawRoundedRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, RectangleEdgeFilter.All);
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, RectangleF rectangle, int radius, RectangleEdgeFilter filter)
    {
      graphics.DrawRoundedRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, filter);
    }

    public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, RectangleF rectangle, int radius)
    {
      graphics.DrawRoundedRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, RectangleEdgeFilter.All);
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, float x, float y, float width, float height, float radius,
                                            RectangleEdgeFilter filter)
    {
      RectangleF rectangle = new RectangleF(x, y, width, height);
      GraphicsPath path = graphics.GenerateRoundedRectangle(rectangle, radius, filter);
      SmoothingMode old = graphics.SmoothingMode;
      graphics.SmoothingMode = SmoothingMode.HighQuality;
      graphics.FillPath(brush, path);
      graphics.SmoothingMode = old;
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, float x, float y, float width, float height, float radius)
    {
      graphics.FillRoundedRectangle(brush, x, y, width, height, radius, RectangleEdgeFilter.All);
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, int x, int y, int width, int height, int radius)
    {
      graphics.FillRoundedRectangle(brush, Convert.ToSingle(x), Convert.ToSingle(y), Convert.ToSingle(width), Convert.ToSingle(height), Convert.ToSingle(radius));
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, Rectangle rectangle, int radius, RectangleEdgeFilter filter)
    {
      graphics.FillRoundedRectangle(brush, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, filter);
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, Rectangle rectangle, int radius)
    {
      graphics.FillRoundedRectangle(brush, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, RectangleEdgeFilter.All);
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, RectangleF rectangle, int radius, RectangleEdgeFilter filter)
    {
      graphics.FillRoundedRectangle(brush, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, filter);
    }

    public static void FillRoundedRectangle(this Graphics graphics, Brush brush, RectangleF rectangle, int radius)
    {
      graphics.FillRoundedRectangle(brush, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, radius, RectangleEdgeFilter.All);
    }

    public static FontMetrics GetFontMetrics(this Graphics graphics, Font font)
    {
      return FontMetricsImpl.GetFontMetrics(graphics, font);
    }

    private static GraphicsPath GenerateRoundedRectangle(this Graphics graphics, RectangleF rectangle, float radius, RectangleEdgeFilter filter)
    {
      float diameter;
      GraphicsPath path = new GraphicsPath();
      if (radius <= 0.0F || filter == RectangleEdgeFilter.None)
      {
        path.AddRectangle(rectangle);
        path.CloseFigure();
        return path;
      }
      if (radius >= (Math.Min(rectangle.Width, rectangle.Height)) / 2.0)
        return graphics.GenerateCapsule(rectangle);
      diameter = radius * 2.0F;
      SizeF sizeF = new SizeF(diameter, diameter);
      RectangleF arc = new RectangleF(rectangle.Location, sizeF);
      if ((RectangleEdgeFilter.TopLeft & filter) == RectangleEdgeFilter.TopLeft)
        path.AddArc(arc, 180, 90);
      else
      {
        path.AddLine(arc.X, arc.Y + arc.Height, arc.X, arc.Y);
        path.AddLine(arc.X, arc.Y, arc.X + arc.Width, arc.Y);
      }
      arc.X = rectangle.Right - diameter;
      if ((RectangleEdgeFilter.TopRight & filter) == RectangleEdgeFilter.TopRight)
        path.AddArc(arc, 270, 90);
      else
      {
        path.AddLine(arc.X, arc.Y, arc.X + arc.Width, arc.Y);
        path.AddLine(arc.X + arc.Width, arc.Y + arc.Height, arc.X + arc.Width, arc.Y);
      }
      arc.Y = rectangle.Bottom - diameter;
      if ((RectangleEdgeFilter.BottomRight & filter) == RectangleEdgeFilter.BottomRight)
        path.AddArc(arc, 0, 90);
      else
      {
        path.AddLine(arc.X + arc.Width, arc.Y, arc.X + arc.Width, arc.Y + arc.Height);
        path.AddLine(arc.X, arc.Y + arc.Height, arc.X + arc.Width, arc.Y + arc.Height);
      }
      arc.X = rectangle.Left;
      if ((RectangleEdgeFilter.BottomLeft & filter) == RectangleEdgeFilter.BottomLeft)
        path.AddArc(arc, 90, 90);
      else
      {
        path.AddLine(arc.X + arc.Width, arc.Y + arc.Height, arc.X, arc.Y + arc.Height);
        path.AddLine(arc.X, arc.Y + arc.Height, arc.X, arc.Y);
      }
      path.CloseFigure();
      return path;
    }

    private static GraphicsPath GenerateCapsule(this Graphics graphics, RectangleF rectangle)
    {
      float diameter;
      RectangleF arc;
      GraphicsPath path = new GraphicsPath();
      try
      {
        if (rectangle.Width <= 0f)
          rectangle.Width = 1f;

        if (rectangle.Height <= 0f)
          rectangle.Height = 1f;

        if (rectangle.Width > rectangle.Height)
        {
          diameter = rectangle.Height;
          SizeF sizeF = new SizeF(diameter, diameter);
          arc = new RectangleF(rectangle.Location, sizeF);
          path.AddArc(arc, 90, 180);
          arc.X = rectangle.Right - diameter;
          path.AddArc(arc, 270, 180);
        }
        else if (rectangle.Width < rectangle.Height)
        {
          diameter = rectangle.Width;
          SizeF sizeF = new SizeF(diameter, diameter);
          arc = new RectangleF(rectangle.Location, sizeF);
          path.AddArc(arc, 180, 180);
          arc.Y = rectangle.Bottom - diameter;
          path.AddArc(arc, 0, 180);
        }
        else
          path.AddEllipse(rectangle);
      }
      catch
      {
        path.AddEllipse(rectangle);
      }
      finally
      {
        path.CloseFigure();
      }
      return path;
    }

    #region Nested type: FontMetricsImpl

    private class FontMetricsImpl : FontMetrics
    {
      private TEXTMETRIC metrics;

      public override int Height
      {
        get { return this.metrics.tmHeight; }
      }

      public override int Ascent
      {
        get { return this.metrics.tmAscent; }
      }

      public override int Descent
      {
        get { return this.metrics.tmDescent; }
      }

      public override int InternalLeading
      {
        get { return this.metrics.tmInternalLeading; }
      }

      public override int ExternalLeading
      {
        get { return this.metrics.tmExternalLeading; }
      }

      public override int AverageCharacterWidth
      {
        get { return this.metrics.tmAveCharWidth; }
      }

      public override int MaximumCharacterWidth
      {
        get { return this.metrics.tmMaxCharWidth; }
      }

      public override int Weight
      {
        get { return this.metrics.tmWeight; }
      }

      public override int Overhang
      {
        get { return this.metrics.tmOverhang; }
      }

      public override int DigitizedAspectX
      {
        get { return this.metrics.tmDigitizedAspectX; }
      }

      public override int DigitizedAspectY
      {
        get { return this.metrics.tmDigitizedAspectY; }
      }

      private FontMetricsImpl(Graphics graphics, Font font)
      {
        this.metrics = this.GenerateTextMetrics(graphics, font);
      }

      [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
      public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

      [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
      public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);

      [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
      public static extern bool DeleteObject(IntPtr hdc);

      public static FontMetrics GetFontMetrics(Graphics graphics, Font font)
      {
        return new FontMetricsImpl(graphics, font);
      }

      private TEXTMETRIC GenerateTextMetrics(Graphics graphics, Font font)
      {
        IntPtr hDC = IntPtr.Zero;
        TEXTMETRIC textMetric;
        IntPtr hFont = IntPtr.Zero;
        try
        {
          hDC = graphics.GetHdc();
          hFont = font.ToHfont();
          IntPtr hFontDefault = SelectObject(hDC, hFont);
          bool result = GetTextMetrics(hDC, out textMetric);
          SelectObject(hDC, hFontDefault);
        }
        finally
        {
          if (hFont != IntPtr.Zero)
            DeleteObject(hFont);
          if (hDC != IntPtr.Zero)
            graphics.ReleaseHdc(hDC);
        }
        return textMetric;
      }

      #region Nested type: TEXTMETRIC

      [StructLayout(LayoutKind.Sequential)]
      public struct TEXTMETRIC
      {
        public readonly int tmHeight;
        public readonly int tmAscent;
        public readonly int tmDescent;
        public readonly int tmInternalLeading;
        public readonly int tmExternalLeading;
        public readonly int tmAveCharWidth;
        public readonly int tmMaxCharWidth;
        public readonly int tmWeight;
        public readonly int tmOverhang;
        public readonly int tmDigitizedAspectX;
        public readonly int tmDigitizedAspectY;
        public readonly char tmFirstChar;
        public readonly char tmLastChar;
        public readonly char tmDefaultChar;
        public readonly char tmBreakChar;
        public readonly byte tmItalic;
        public readonly byte tmUnderlined;
        public readonly byte tmStruckOut;
        public readonly byte tmPitchAndFamily;
        public readonly byte tmCharSet;
      }

      #endregion
    }

    #endregion
  }

  public enum RectangleEdgeFilter
  {
    None = 0,
    TopLeft = 1,
    TopRight = 2,
    BottomLeft = 4,
    BottomRight = 8,
    All = TopLeft | TopRight | BottomLeft | BottomRight
  }

  public abstract class FontMetrics
  {
    public virtual int Height
    {
      get { return 0; }
    }

    public virtual int Ascent
    {
      get { return 0; }
    }

    public virtual int Descent
    {
      get { return 0; }
    }

    public virtual int InternalLeading
    {
      get { return 0; }
    }

    public virtual int ExternalLeading
    {
      get { return 0; }
    }

    public virtual int AverageCharacterWidth
    {
      get { return 0; }
    }

    public virtual int MaximumCharacterWidth
    {
      get { return 0; }
    }

    public virtual int Weight
    {
      get { return 0; }
    }

    public virtual int Overhang
    {
      get { return 0; }
    }

    public virtual int DigitizedAspectX
    {
      get { return 0; }
    }

    public virtual int DigitizedAspectY
    {
      get { return 0; }
    }
  }
}